﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Content.Pipeline.Serialization.Intermediate;
using Microsoft.Xna.Framework.Graphics;
using System.Xml;
using System.Windows.Forms;
using System.Threading;


namespace Jemgine.MapEditor
{
	public partial class EditorState
	{
        public int LoadingLock = 0;

        internal Controls.HelpWindow HelpWindow = null;

        internal void SpawnHelpWindow(String Topic)
        {
            if (HelpWindow == null) HelpWindow = new Controls.HelpWindow();
            HelpWindow.SetEditorState(this);
            HelpWindow.SelectTopic(Topic);
            HelpWindow.Show();
            if (HelpWindow.WindowState == FormWindowState.Minimized)
                HelpWindow.WindowState = FormWindowState.Normal;
            HelpWindow.Activate();
        }

        public bool Changes = false;
		public bool CtrlHeld = false;
        public bool AltHeld = false;

		public Config _config;

		public Action OnInvalidateMap;
		public Action OnMapLoaded;
		public Action<string> OnConsoleOutput;
		public Action OnSelection;
        public Action OnFileSet;

		public TreeNodeCollection Tree;
		public Action TreeChanged;

		internal Engine.Selection Selected = Engine.Selection.None;
        internal List<Engine.Selection> MultiSelection = new List<Jemgine.Engine.Selection>();
        internal Tool SelectedTool = null;

        internal List<Engine.Selection> GetEverythingSelected()
        {
            var R = new List<Engine.Selection>(MultiSelection);
            R.Add(Selected);
            return R.Distinct().ToList();
        }

        internal void SelectItem(Engine.Selection _select)
		{
			Selected = _select;
            MultiSelection.Clear();
			if (OnSelection != null) OnSelection();
			if (OnInvalidateMap != null) OnInvalidateMap();
		}

        internal void SelectItemEx(Engine.Selection _select)
        {
            Selected = _select;
            if (OnSelection != null) OnSelection();
            if (OnInvalidateMap != null) OnInvalidateMap();
        }

        internal bool PointInItem(Vector2 Dimensions, Vector2 ItemPosition, Vector2 Point)
        {
            ItemPosition -= Dimensions / 2;

            if (Point.X >= ItemPosition.X && Point.X < ItemPosition.X + Dimensions.X &&
                Point.Y >= ItemPosition.Y && Point.Y < ItemPosition.Y + Dimensions.Y) return true;

            return false;
        }

        internal List<Engine.Selection> GetElementStack(Vector2 Position)
        {
            List<Engine.Selection> Result = new List<Engine.Selection>();
            Vector2 InteriorPosition;
            Data.EntityRecord Entity;

            if (Selected.Type == Engine.Selection.Types.Vertex)
            {
                Entity = GetTopObject(Selected) as Data.EntityRecord;
                var Poly = Entity.AttachedElements[Selected.MiddleIndex] as Data.HasVerticies;

                InteriorPosition = Position - Entity.Position;

                for (int i = 0; i < Poly.Count; ++i)
                {
                    if (PointInItem(new Vector2(1, 1), Poly.GetVertex(i).Position, InteriorPosition))
                    {

                        Result.Add(new Engine.Selection(Engine.Selection.Types.Vertex,
                            Selected.TopIndex, Selected.MiddleIndex, i));
                    }
                }
            }

            for (int Element = 0; Element < Map.Elements.Count; ++Element)
            {
                Data.MapElement MapElement = Map.Elements[Element];

                if (MapElement is Data.EntityRecord)
                {
                    if (PointInItem(MapElement.GetDimensions(), MapElement.GetPosition(), Position))
                        Result.Add(new Engine.Selection(Engine.Selection.Types.MapElement, Element, 0, 0));

                    Entity = MapElement as Data.EntityRecord;
                    InteriorPosition = Position - Entity.Position;
                    for (int EntityElement = 0; EntityElement < Entity.AttachedElements.Count; ++EntityElement)
                    {
                        var TransformedInteriorPosition = InteriorPosition;
                        var _Element = Entity.AttachedElements[EntityElement];
                        if (_Element.BoneReference != null)
                            TransformedInteriorPosition = Vector2.Transform(TransformedInteriorPosition,
                                Matrix.Invert(_Element.BoneReference.Transform));
                        bool AddedEntityElement = false;
                        if (_Element.PointInside(TransformedInteriorPosition))
                        {
                            Result.Add(new Engine.Selection(Entity.AttachedElements[EntityElement].SelectionType(),
                                Element, EntityElement, 0));
                            AddedEntityElement = true;
                        }

                        if (Selected.TopIndex == Element && Selected.MiddleIndex == EntityElement)
                        {
                            var Polygon = Entity.AttachedElements[EntityElement] as Data.HasVerticies;
                            if (Polygon != null)
                            {
                                for (int Vertex = 0; Vertex < Polygon.Count; ++Vertex)
                                    if (PointInItem(new Vector2(1, 1), Polygon.GetVertex(Vertex).Position, TransformedInteriorPosition))
                                    {
                                        if (!AddedEntityElement)
                                        {
                                            Result.Add(new Engine.Selection(Entity.AttachedElements[EntityElement].SelectionType(),
                                                Element, EntityElement, 0));
                                            AddedEntityElement = true;
                                        }

                                        Result.Add(new Engine.Selection(Engine.Selection.Types.Vertex,
                                            Element, EntityElement, Vertex));
                                    }
                            }


                        }
                    }
                }
                else if (MapElement is Data.Dynamic)
                {
                    Data.Dynamic Dynamic = MapElement as Data.Dynamic;
                    if (PointInItem(MapElement.GetDimensions(), MapElement.GetPosition(), Position))
                        Result.Add(new Engine.Selection(Engine.Selection.Types.MapElement, Element, 0, 0));
                    for (int Anchor = 0; Anchor < Dynamic.Anchors.Count; ++Anchor)
                        if (PointInItem(new Vector2(1, 1), Dynamic.Anchors[Anchor].Position, Position))
                            Result.Add(new Engine.Selection(Engine.Selection.Types.Anchor, Element, Anchor, 0));
                }
                else if (MapElement is Data.ScriptNode && PointInItem(MapElement.GetDimensions(), MapElement.GetPosition(), Position))
                    Result.Add(new Engine.Selection(MapElement.SelectionType(), Element, 0, 0));
            }
            return Result;
        }

        internal List<Data.EntityElement> GetLocalElementStack(Vector2 Position, Data.EntityRecord Entity)
        {
            var Result = new List<Data.EntityElement>();
            Vector2 InteriorPosition = Position - Entity.Position;
            foreach (var Element in Entity.AttachedElements)
            {
                Vector2 IP = InteriorPosition;
                if (Element.BoneReference != null) IP = Vector2.Transform(IP, Matrix.Invert(Element.BoneReference.Transform));
                if (Element.PointInside(IP)) Result.Add(Element);
            }
            return Result;
        }

        internal Action<List<Operation>> OnOperationStackChange;
		private List<Operation> Operations = new List<Operation>();

		internal void PerformOperation(Operation operation)
		{
			Operation.ReturnCode Code = operation.Do(this);

			if (Code == Operation.ReturnCode.AddToUndoStack)
			{
                Changes = true;
                Operations.Add(operation);
                if (Operations.Count > _config.MaxUndoStackSize) Operations.RemoveAt(0);
                if (OnOperationStackChange != null) OnOperationStackChange(Operations);

                //Operations.Push(operation);
                ////if (Operations.Count > MaxUndoStackSize) Operations.Pop
                //if (OnDidOperation != null) OnDidOperation(operation.GetType().Name);
			}

			OnInvalidateMap();
		}

		internal void UndoOperation()
		{
			if (Operations.Count > 0)
			{
                Changes = true;
                Operation ToUndo = Operations[Operations.Count - 1];
                ToUndo.Undo(this);
                Operations.RemoveAt(Operations.Count - 1);
                if (OnOperationStackChange != null) OnOperationStackChange(Operations);
			}

			OnInvalidateMap();
		}

        internal void ClearUndoList()
        {
            Operations.Clear();
            if (OnOperationStackChange != null) OnOperationStackChange(Operations);
        }

		private String _FileName;
		public String FileName { get { return _FileName; } }

		private String _Directory;
		public String Directory { get { return _Directory; } }

        public bool FileSet { get; private set; }

		private void SetFile(String FileName)
		{
			_FileName = FileName;
			_Directory = System.IO.Path.GetDirectoryName(FileName);
		}

        public Vector2 SnapToGrid(Vector2 V)
        {
            V /= _config.GridSpacing;
            V.X = (float)System.Math.Round(V.X);
            V.Y = (float)System.Math.Round(V.Y);
            V *= _config.GridSpacing;
            return V;
        }

		/// <summary>
		/// Returns the path relative the directory of the currently open map.
		/// Assumes the resource is in the same or a subdir of the working directory.
		/// </summary>
		/// <param name="FileName"></param>
		/// <returns></returns>
		public static String GetAssetName(String FileName)
		{
			return FileName.Substring(0, FileName.IndexOf('.'));
		}

		public String GetRelativeResourceName(String FileName)
		{
			String FirstPart = FileName.Substring(0, Directory.Length);
			if (FirstPart != Directory) throw new InvalidProgramException("Resources must be in subdir.");
			return FileName.Substring(Directory.Length + 1);
		}


		public Data.MapData Map;
        public Data.MapData ClipBoard;
		public Jemgine.Engine.Renderer.Camera Camera;
		public Vector2 RawMousePosition;
		public Vector2 MouseDelta;

		internal OutputBuffer _errorBuffer = new OutputBuffer();

		public EditorState()
		{
            LoadingLock = 1;

            try
            {
                String ConfigFilename = PrepareConfigFile();
                XmlReader Reader = XmlReader.Create(ConfigFilename);
                _config = IntermediateSerializer.Deserialize<Config>(Reader, ConfigFilename);
                Reader.Close();
                _errorBuffer.Output("Loaded config file.");
            }
            catch (Exception e)
            {
                _config = new Config();
                _errorBuffer.Output(e.Message);
            }

			_config._loadedAssemblies.Add(
				System.Reflection.Assembly.GetAssembly(typeof(Jemgine.Engine.Simulation)));
            _errorBuffer.Output("(Added engine assembly.)");
            _config._loadedAssemblies.Add(
                System.Reflection.Assembly.GetAssembly(typeof(Jemgine.MapEditor.EditorState)));
            _errorBuffer.Output("(Added editor assembly.)");

			foreach (AssemblyConfig _assemblyName in _config.Assemblies)
			{
				try
				{
					_config._loadedAssemblies.Add(
						System.Reflection.Assembly.LoadFile(new System.IO.FileInfo(_assemblyName.File).FullName));
					_errorBuffer.Output("Loaded assembly " + _assemblyName.File);
				}
				catch (Exception e)
				{
					_errorBuffer.Output("Error loading assembly " + _assemblyName.File + " : " + e.Message);
				}
			}

			
            LoadingLock = 0;
		}

       

        private static String PrepareConfigFile()
		{
			System.IO.Directory.CreateDirectory(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)
				+ "\\Jemgine\\Editor");
			String ConfigFilename = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)
							+ "\\Jemgine\\Editor\\config.txt";
			return ConfigFilename;
		}

		public void NewMap()
		{
            LoadingLock++;

			Map = new Data.MapData();
			SetFile("untitled.map");
			ResetState();
            FileSet = false;
            if (OnFileSet != null) OnFileSet();
            if (OnConsoleOutput != null) OnConsoleOutput("\r\nNew map.\r\n");

            LoadingLock--;
		}

		public void OpenMap(String filename)
		{
            LoadingLock++;

            OnConsoleOutput("\r\nLoad map " + filename + "\r\n");

            Jemgine.Data.MapData NewMap = null;

            try
            {
                XmlReader Reader = XmlReader.Create(filename);
                NewMap = IntermediateSerializer.Deserialize<Jemgine.Data.MapData>(Reader, filename);
                Reader.Close();
            }
            catch (Exception e)
            {
                OnConsoleOutput(e.Message);
                LoadingLock--;
                return;
            }

			SetFile(filename);
            FileSet = true;
            if (OnFileSet != null) OnFileSet();

            Map = NewMap;		

			//ResetState();
            //SelectedTexture = "";
            this.SelectItem(Jemgine.Engine.Selection.None);
            Operations.Clear();
            MultiSelection.Clear();

            Map.PostProcess(null, Vector2.Zero, null);

            foreach (var Element in Map.Elements)
            {
                var Attrs = Element.GetType().GetCustomAttributes(true);
                foreach (var Attr in Attrs)
                    if (Attr is Engine.EditorInitializationAttribute)
                        (Attr as Engine.EditorInitializationAttribute).Initialize_EditorLoad(Element, new EditorStateInterface(this));
            }

            if (OnOperationStackChange != null) OnOperationStackChange(Operations);
            _RebuildTree();
            if (OnMapLoaded != null) OnMapLoaded();

            OnConsoleOutput("\r\nDone loading.\r\n");

            LoadingLock--;
		}

		public void ResetState()
		{
			//SelectedTexture = "";
            this.SelectItem(Jemgine.Engine.Selection.None);
            MultiSelection.Clear();
            Operations.Clear();
            if (OnOperationStackChange != null) OnOperationStackChange(Operations);
            //TextureTable.Clear();
            _RebuildTree();
			if (OnMapLoaded != null) OnMapLoaded();
		}

		public void SaveMap()
		{
            if (!FileSet) throw new InvalidOperationException();
            Map.PreProcess();
			XmlWriterSettings Settings = new XmlWriterSettings();
			Settings.Indent = true;
			XmlWriter Writer = XmlWriter.Create(FileName, Settings);
			IntermediateSerializer.Serialize<Jemgine.Data.MapData>(Writer, Map, FileName);
			Writer.Close();
            Changes = false;
		}

		public void SaveConfig()
		{
			String ConfigFilename = PrepareConfigFile();

			XmlWriterSettings Settings = new XmlWriterSettings();
			Settings.Indent = true;
			XmlWriter Writer = XmlWriter.Create(ConfigFilename, Settings);
			IntermediateSerializer.Serialize<Config>(Writer, _config, ConfigFilename);
			Writer.Close();
		}

		public void SaveMapAs(String filename)
		{
			SetFile(filename);
            FileSet = true;
            if (OnFileSet != null) OnFileSet();
			SaveMap();
		}

		internal void CalculateMousePosition(Vector2 RawMousePos)
		{
			RawMousePos = Camera.UnProject(RawMousePos);
			MouseDelta = RawMousePos - RawMousePosition;
			RawMousePosition = RawMousePos;
		}

	}
}
